package org.fastsyncer.parser.framework;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.fastsyncer.common.constant.ConvertConstant;
import org.fastsyncer.common.constant.MappingConstant;
import org.fastsyncer.common.entity.Mapping;
import org.fastsyncer.common.entity.MappingTask;
import org.fastsyncer.common.util.AESEncyptUtil;
import org.fastsyncer.common.util.SHA1Util;
import org.fastsyncer.common.util.UUIDUtil;
import org.fastsyncer.plugin.Convert;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <ul>
 *  <dl>
 *      <li>1、提供插件加载</li>
 *      <li>2、自定策略转换</li>
 *      <li>3、根据条件过滤</li>
 *  </dl>
 * </ul>
 * 
 * @author AE86
 * @date 2018年9月3日 下午3:37:51
 * @version 1.0.0
 */
public class ConvertFactory {

    private final static Logger logger = LoggerFactory.getLogger(ConvertFactory.class);
    
    private final static Map<String, Convert> proxy = new HashMap<String, Convert>();

    private ConvertFactory(){
    }
    
    public static Convert getInstance(String className) {
        Convert convert = proxy.get(className);
        // 如果未创建或实例,重新加载class
        if (null == convert) {
            try {
                convert = (Convert) Class.forName(className).newInstance();
                proxy.put(className, convert);
            } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            }
        }
        return convert;
    }
    
    /**
     * 批量转换数据源
     * @param mappingTask
     * @param rows
     */
    public static List<Map<String, Object>> convert(MappingTask task, List<Map<String, Object>> source) throws Exception {
        // 将数据源集合转换为目标源集合
        List<Map<String, Object>> target = new ArrayList<Map<String, Object>>();
        if (source == null || source.isEmpty()) {
            return source;
        }

        // 1.获取同步字段
        Mapping sourceMapping = task.getSourceMapping();
        Mapping targetMapping = task.getTargetMapping();
        List<Map<String, Object>> sfileds = sourceMapping.getFileds();
        List<Map<String, Object>> tfileds = targetMapping.getFileds();

        // 2.获取目标源字段与数据源字段映射关系
        List<String> souFileds = new ArrayList<>();
        List<String> tarFileds = new ArrayList<>();
        int size = tfileds.size();
        for (int i = 0; i < size; i++) {
            souFileds.add((String) sfileds.get(i).get("name"));
            tarFileds.add((String) tfileds.get(i).get("name"));
        }

        // 3.转换配置
        Map<String, List<Map<String, String[]>>> convertor = targetMapping.getConvertor();
        final boolean isConvertor = convertor != null && !convertor.isEmpty();
        // 假设条件如下：
        // ID   ID
        // NAME NAME
        // NAME USERNAME
        // row> {id=123,name=张三}  期望实现  {username=张三,name=张三,id=123}
        // 4.遍历数据源
        Map<String, Object> r;
        String tName;
        Object val;
        Object col;
        List<Map<String, String[]>> convertTypes;
        for (Map<String, Object> row : source) {
            // 目标源数据
            r = new HashMap<>();
            for (int i = 0; i < size; i++) {
                // 4.1获取目标字段对应数据源字段的值
                tName = tarFileds.get(i);
                // 如果该字段为空,排除该属性同步
                if (StringUtils.isBlank(tName)) {
                    continue;
                }
                val = row.get(souFileds.get(i));

                // 4.2转换值
                if (isConvertor) {
                    // 获取目标源转换策略
                    convertTypes = convertor.get(String.valueOf(i));
                    if (null != convertTypes) {
                        val = parse(convertTypes, val);
                    }
                }

                // 4.3设置目标字段和转换后的值
                /**
                 * 如果数据源有多个字段映射到目标源
                 * 例如映射关系如下：
                 *  数据源                             目标源
                 *  firstname   > username
                 *  lastname    > username
                 *  
                 * 假设数据源的值如下：
                 *  firstname   lastname
                 *  张                               三
                 * 那么转换后效果如下：
                 *  username
                 *  张三
                 */
                col = r.get(tName);
                // 如果未设置值,直接保存
                if (null == col) {
                    r.put(tName, val);
                    continue;
                }
                // 如果发现有重复的目标源字段,但是当前转换的值为null,就不处理
                if (null == val) {
                    continue;
                }
                // 如果重复,直接追加
                r.put(tName, col.toString() + val.toString());
            }
            target.add(r);
        }
        souFileds = null;
        tarFileds = null;

        // 5.转换策略
        Map<String, String> map = task.getPolicy().get(MappingConstant.POLICY_CONVERT);
        if (null != map && null != source && !source.isEmpty()) {
            try {
                getConvert(map).convert(task, source, target);
            } catch (IllegalArgumentException e) {
                logger.error(e.getClass() + " >> " + e.getLocalizedMessage());
            }
        }
        source = null;
        return target;
    }

    /**
     * 转换数据源
     * @param task 驱动配置
     * @param event 事件新增、修改、删除
     * @param row 增量数据
     * @param beforeData 变化前数据
     * @return
     * @throws JSONException
     */
    public static Map<String, Object> convert(MappingTask task, String event, Map<String, Object> row, Map<String, Object> beforeData) throws Exception {
        // 1.将数据源集合转换为目标源集合
        Map<String, Object> target = new HashMap<String, Object>();
        Mapping sourceMapping = task.getSourceMapping();
        Mapping targetMapping = task.getTargetMapping();

        // 2.获取目标源字段与数据源字段映射关系
        List<Map<String, Object>> sfileds = sourceMapping.getFileds();
        List<Map<String, Object>> tfileds = targetMapping.getFileds();
        int size = tfileds.size();
        List<String> souFileds = new ArrayList<>();
        List<String> tarFileds = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            souFileds.add((String) sfileds.get(i).get("name"));
            tarFileds.add((String) tfileds.get(i).get("name"));
        }

        // 3.转换配置
        Map<String, List<Map<String, String[]>>> convertor = targetMapping.getConvertor();
        final boolean isConvertor = convertor != null && !convertor.isEmpty();

        // 目标源数据
        String tName;
        Object val;
        Object col;
        List<Map<String, String[]>> convertTypes;
        for (int i = 0; i < size; i++) {
            // 4.1获取目标字段对应数据源字段的值
            tName = tarFileds.get(i);
            // 如果该字段为空,排除该属性同步
            if (StringUtils.isBlank(tName)) {
                continue;
            }
            val = row.get(souFileds.get(i));
            // 防止值为数组
            if (val instanceof JSONArray) {
                // 用“,”拼接值返回
                JSONArray arr = (JSONArray) val;
                val = concatValFromJSONArray(arr);
            }

            // 4.2转换值
            if (isConvertor) {
                // 获取目标源转换策略
                convertTypes = convertor.get(String.valueOf(i));
                if (null != convertTypes) {
                    val = parse(convertTypes, val);
                }
            }

            // 4.3设置目标字段和转换后的值
            /**
             * 如果数据源有多个字段映射到目标源
             * 例如映射关系如下：
             *  数据源                             目标源
             *  firstname   > username
             *  lastname    > username
             *  
             * 假设数据源的值如下：
             *  firstname   lastname
             *  张                               三
             * 那么转换后效果如下：
             *  username
             *  张三
             */
            col = target.get(tName);
            // 如果未设置值,直接保存
            if (null == col) {
                target.put(tName, val);
                continue;
            }
            // 如果发现有重复的目标源字段,但是当前转换的值为null,就不处理
            if (null == val) {
                continue;
            }
            // 如果重复,直接追加
            target.put(tName, col.toString() + val.toString());
        }

        souFileds = null;
        tarFileds = null;

        // 5.转换策略
        Map<String, String> map = task.getPolicy().get(MappingConstant.POLICY_CONVERT);
        if (null != map && null != row && !row.isEmpty()) {
            try {
                getConvert(map).convert(task, row, target, event, beforeData);
            } catch (IllegalArgumentException e) {
                logger.error(e.getClass() + " >> " + e.getLocalizedMessage());
            }
        }
        row = null;
        return target;
    }

    private static Convert getConvert(Map<String, String> policy) throws IllegalArgumentException {
        // 2.自定义转换策略实现
        String className = policy.get(MappingConstant.POLICY_CONVERT_CLASSNAME);
        if (StringUtils.isBlank(className)) {
            throw new IllegalArgumentException("Can not find implements class of \"" + className + "\"");
        }
        // 3.获取转换器
        Convert factory = getInstance(className);
        if (null == factory) {
            logger.error("Can not find the implements class of \"" + className + "\", please retry it after check policy.");
            throw new IllegalArgumentException("Can not find implements class of \"" + className + "\"");
        }
        return factory;
    }
    
    /**
     * 将JSONArray转换为map对象
     * @param row
     * @return map
     * @throws JSONException 
     */
    public static Map<String, Object> convertJSONArrayToMap(JSONArray row) throws JSONException {
        if (null == row) {
            return null;
        }
        int len = row.length();
        if (1 > len) {
            return null;
        }
        Map<String, Object> r = new HashMap<>();
        JSONObject o;
        String k;
        Object v;
        for (int i = 0; i < len; i++) {
            o = row.getJSONObject(i);
            k = o.getString("name");
            v = o.isNull("value") ? null : o.get("value");
            r.put(k, v);
        }
        return r;
    }

    // 用“,”拼接值返回
    private static String concatValFromJSONArray(JSONArray arr) throws JSONException {
        StringBuilder val = new StringBuilder();
        int jLen = arr.length();
        int end = jLen - 1;
        for (int j = 0; j < jLen; j++) {
            // 如果不是最后的一个参数,追加“,”
            if (j < end) {
                val.append(arr.getString(j)).append(",");
                continue;
            }
            val.append(arr.getString(j));
        }
        return val.toString();
    }

    /**
     * 数据转换
     * @Title: convert 
     * @Description: 数据转换,根据类型转换值
     * @param convertTypes 根据类型
     * @param value 值
     * @return: String 转换后的值
     */
    private static Object parse(List<Map<String, String[]>> convertTypes, Object value) {
        // 遍历处理方式[append,replace,append,defaultTimestamp]
        for (Map<String, String[]> map : convertTypes) {
            // append : ["asd"]
            for (Map.Entry<String, String[]> opr : map.entrySet()) {
                String key = opr.getKey();
                String[] arg = opr.getValue();
                switch (key) {
                case ConvertConstant.CONVERT_TYPE_DEFAULT:
                    // 默认
                    if (null == value || "".equals(value.toString().trim())) {
                        value = arg[0];
                    }
                    break;
                case ConvertConstant.CONVERT_TYPE_DEFAULT_TIMESTAMP:
                    // 默认时间戳
                    if (null == value || "".equals(value.toString().trim())) {
                        value = new Timestamp(System.currentTimeMillis());
                    }
                    break;
                case ConvertConstant.CONVERT_TYPE_REPLACE:
                    // 替换,例如“张?三”替换?为空字符串 => 张三
                    if (null == value) {
                        value = "";
                    }
                    value = value.toString().trim().replaceAll(arg[0], arg[1]);
                    break;
                case ConvertConstant.CONVERT_TYPE_PREPEND:
                    // 追加在前面,例如“张三”追加123 => 123张三
                    if (null == value) {
                        value = "";
                    }
                    value = arg[0] + value.toString().trim();
                    break;
                case ConvertConstant.CONVERT_TYPE_APPEND:
                    // 追加在后面,例如“张三”追加123 => 张三123
                    if (null == value) {
                        value = "";
                    }
                    value = value.toString().trim() + arg[0];
                    break;
                case ConvertConstant.CONVERT_TYPE_AES_ENCRYPT:
                    // AES加密
                    if (null != value && !"".equals(value.toString().trim())) {
                        try {
                            value = AESEncyptUtil.encrypt(value.toString().trim(), arg[0]);
                        } catch (Exception e) {
                            logger.error("AESEncyptUtil encrypt error:" + e.getMessage() + " >> " + e.getLocalizedMessage());
                        }
                    }
                    break;
                case ConvertConstant.CONVERT_TYPE_AES_DECRYPT:
                    // AES解密
                    if (null != value && !"".equals(value.toString().trim())) {
                        try {
                            value = AESEncyptUtil.decrypt(value.toString().trim(), arg[0]);
                        } catch (Exception e) {
                            logger.error("AESEncyptUtil decrypt error:" + e.getMessage() + " >> " + e.getLocalizedMessage());
                        }
                    }
                    break;
                case ConvertConstant.CONVERT_TYPE_SHA1:
                    // SHA1加密
                    if (null != value && !"".equals(value.toString().trim())) {
                        value = SHA1Util.b64_sha1(value.toString().trim());
                    }
                    break;
                case ConvertConstant.CONVERT_TYPE_UUID:
                    // UUID
                    value = UUIDUtil.getUUID();
                    break;
                case ConvertConstant.CONVERT_TYPE_REM_FIRST:
                    // 去掉首字符
                    if(null != value){
                        String remF = value.toString().trim();
                        if (StringUtils.isNotBlank(remF)) {
                            value = remF.substring(1, remF.length());
                        }
                    }
                    break;
                case ConvertConstant.CONVERT_TYPE_REM_LAST:
                    // 去掉尾字符
                    if(null != value){
                        String remL = value.toString().trim();
                        if (StringUtils.isNotBlank(remL)) {
                            value = remL.substring(0, remL.length() - 1);
                        }
                    }
                    break;
                case ConvertConstant.CONVERT_TYPE_SUB_FIRST:
                    // 从前面截取N个字符
                    if(null != value){
                        String subF = value.toString().trim();
                        if (StringUtils.isNotBlank(subF) && NumberUtils.isNumber(arg[0])) {
                            int subFMax = Integer.parseInt(arg[0]);
                            if (subFMax > 0 && subF.length() > subFMax) {
                                value = subF.substring(0, subFMax);
                            }
                        }
                    }
                    break;
                case ConvertConstant.CONVERT_TYPE_SUB_LAST:
                    // 从后面截取N个字符
                    if(null != value){
                        String subL = value.toString().trim();
                        if (StringUtils.isNotBlank(subL) && NumberUtils.isNumber(arg[0])) {
                            int subLMax = Integer.parseInt(arg[0]);
                            int subLL = subL.length();
                            if (subLMax > 0 && subLL > subLMax) {
                                value = subL.substring(subLL - subLMax);
                            }
                        }
                    }
                    break;
                case ConvertConstant.CONVERT_TYPE_PICK_UP_NUM:
                    // 补位,例如“0000” => 0012
                    value = pickUpNumber(arg, value);
                    break;
                case ConvertConstant.CONVERT_TYPE_CLEAR:
                    // 清空
                    value = "";
                    break;
                default:
                    break;
                }
            }
        }
        return value;
    }

    /**
     * 补位,例如“0000” => 0012
     * @param arg
     * @param value
     */
    private static Object pickUpNumber(String[] arg, Object value) {
        // 排除空或非数字类型
        if (null == value) {
            return value;
        }
        String v = value.toString().trim();
        if (!NumberUtils.isNumber(v)) {
            return value;
        }

        // 000000
        String n = arg[0];
        int zeroNum = n.length();

        // 判断是否需要补位
        int i = zeroNum - v.length();
        if (1 > i) {
            return value;
        }
        // 在前面补全0,最长num.length()位,
        // 000123
        // 008373
        // 223455
        return n.substring(0, i) + value;
    }
    
}
